/*!
    \copyright  Copyright (c) 2020 - 2025 Qualcomm Technologies International, Ltd.\n
                All Rights Reserved.\n
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file       usb_audio_fd.c
    \ingroup    usb_audio
    \brief      USB Audio Function Driver
*/

#include "usb_audio_fd.h"
#include "usb_audio_class_10.h"
#include "usb_audio_class_20.h"

#include <panic.h>
#include "kymera.h"
#include "volume_messages.h"
#include "telephony_messages.h"
#include "ui.h"
#include <device.h>
#include <device_list.h>
#include "device_types.h"
#include "device_properties.h"

#include "usb_source.h"
#include "usb_audio_source_audio_if/usb_audio_source_audio_if.h"
#include "usb_audio_source_voice_if/usb_audio_source_voice_if.h"



usb_audio_info_t *usbaudio_globaldata = NULL;

static void UsbAudio_HandleMessage(usb_device_index_t device_index,
                                     MessageId id, Message message);
static void UsbAudio_ClassEvent(uac_ctx_t class_ctx,
                                uint8 interface_index,
                                uac_message_t uac_message);
static void usbAudioUpdateHeadphoneVolume(usb_audio_info_t *usb_audio);
static void usbAudioUpdateHeadsetVolume(usb_audio_info_t *usb_audio);
static uint8 usbAudio_VolumeToSteps(const usb_audio_volume_config_t *volume_config,
                                    int8 volume_in_db, uint8 mute_status);




/****************************************************************************
    Initialize and add USB Voice Class Driver
*/
static void usbAudioAddHeadset(usb_audio_info_t *usb_audio)
{
    usb_audio_streaming_info_t *streaming_info;

    DEBUG_LOG_WARN("UsbAudio: headset");

    usb_audio->headset = PanicUnlessNew(usb_audio_headset_info_t);
    memset(usb_audio->headset, 0, sizeof(usb_audio_headset_info_t));

    streaming_info = UsbAudio_GetStreamingInfo(usb_audio,
                                               USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER);
    if (streaming_info)
    {
        usb_audio->headset->spkr_enabled = TRUE;
        usb_audio->headset->spkr_src = StreamUsbEndPointSource(streaming_info->ep_address);
    }

    streaming_info = UsbAudio_GetStreamingInfo(usb_audio,
                                               USB_AUDIO_DEVICE_TYPE_VOICE_MIC);
    if (streaming_info)
    {
        usb_audio->headset->mic_enabled = TRUE;
        usb_audio->headset->mic_sink = StreamUsbEndPointSink(streaming_info->ep_address);
    }

    UsbSource_RegisterVoiceControl();

    usb_audio->headset->audio_source = voice_source_usb;
    /* Register with audio source for Audio use case */
    VoiceSources_RegisterAudioInterface(voice_source_usb,
                              UsbAudioFd_GetSourceVoiceInterface());

    UsbAudio_RegisterVoiceSourceInterface(voice_source_usb);

    /* Register with volume source for Audio use case */
    VoiceSources_RegisterVolume(voice_source_usb,
        UsbAudioFd_GetVoiceSourceVolumeInterface());

    /* Init default volume for USB Voice */
    usb_audio->headset->spkr_volume_steps =
            usbAudio_VolumeToSteps(&usb_audio->config->volume_config,
                                   usb_audio->config->volume_config.target_db, 0 /* mute */);
}

/****************************************************************************
    Initialize and add USB Audio Class Driver
*/
static void usbAudioAddHeadphone(usb_audio_info_t *usb_audio)
{
    usb_audio_streaming_info_t *streaming_info;

    DEBUG_LOG_WARN("UsbAudio: headphone");

    usb_audio->headphone = PanicUnlessNew(usb_audio_headphone_info_t);
    memset(usb_audio->headphone, 0, sizeof(usb_audio_headphone_info_t));

    streaming_info = UsbAudio_GetStreamingInfo(usb_audio,
                                               USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER);
    if (streaming_info)
    {
        usb_audio->headphone->spkr_enabled = TRUE;
        usb_audio->headphone->spkr_src = StreamUsbEndPointSource(streaming_info->ep_address);
    }

    streaming_info = UsbAudio_GetStreamingInfo(usb_audio,
                                               USB_AUDIO_DEVICE_TYPE_AUDIO_MIC);
    if (streaming_info)
    {
        usb_audio->headphone->mic_enabled = TRUE;
        usb_audio->headphone->mic_sink = StreamUsbEndPointSink(streaming_info->ep_address);
    }

    UsbSource_RegisterAudioControl();

    usb_audio->headphone->audio_source = audio_source_usb;
    /* Register with audio source for Audio use case */
    AudioSources_RegisterAudioInterface(audio_source_usb,
                              UsbAudioFd_GetSourceAudioInterface());

    UsbAudio_RegisterAudioSourceInterface(audio_source_usb);

    /* Register with volume source for Audio use case */
    AudioSources_RegisterVolume(audio_source_usb,
        UsbAudioFd_GetAudioSourceVolumeInterface());

    usb_audio->headphone->spkr_volume_steps =
            usbAudio_VolumeToSteps(&usb_audio->config->volume_config,
                                   usb_audio->config->volume_config.target_db, 0);
}

/****************************************************************************
    Return function table for supported USB class driver
*/
static const usb_fn_tbl_uac_if * usbAudioGetFnTbl(usb_audio_class_rev_t rev)
{
    const usb_fn_tbl_uac_if *tbl = NULL;

    switch(rev)
    {
        case USB_AUDIO_CLASS_REV_1:
            tbl = UsbAudioClass10_GetFnTbl();
            break;

        case USB_AUDIO_CLASS_REV_2:
            tbl = UsbAudioClass20_GetFnTbl();
            break;

        default:
            DEBUG_LOG("uac: unsupported USB Class Revision 0x%x", rev);
            break;
    }

    return tbl;
}

/****************************************************************************
    Update USB Audio/Voice connection based on status of speaker/mic interface of headphone/headset
*/
static void usbAudioUpdateConnections(void)
{
    usb_audio_info_t *headset_audio = UsbAudioFd_GetHeadsetInfo(voice_source_usb);
    usb_audio_info_t *headphone_audio = UsbAudioFd_GetHeadphoneInfo(audio_source_usb);

    if (headset_audio)
    {
        if (headset_audio->headset->mic_active || headset_audio->headset->spkr_active)
        {
#ifdef USB_SUPPORT_HEADPHONE_SPKR_IN_VOICE_CHAIN
            /* headset MIC is active - connect if not already or reconnect
                    * if speaker changed */
            usb_audio_device_type_t new_voice_speaker, prev_voice_speaker;

            prev_voice_speaker = headset_audio->headset->alt_spkr_connected ?
                        USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER:
                        USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER;
            new_voice_speaker = prev_voice_speaker;

            if(headset_audio->headset->spkr_active)
            {
                new_voice_speaker = USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER;
            }
            else if(headphone_audio && headphone_audio->headphone->spkr_active)
            {
                new_voice_speaker = USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER;
            }

            if (new_voice_speaker != prev_voice_speaker)
            {
                UsbAudio_HandleVoiceConfigChanged(headset_audio, USB_AUDIO_STREAMING_INTERFACE_CHANGED_VOICE_IND, FALSE);
            }
#endif

            if (!headset_audio->headset->source_connected)
            {
                UsbAudio_ConnectVoiceMsg(headset_audio);
                /* Update mic mute status and update headset speaker volume level if it is active*/
                usbAudioUpdateHeadsetVolume(headset_audio);
#ifdef USB_SUPPORT_HEADPHONE_SPKR_IN_VOICE_CHAIN
                if (new_voice_speaker == USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER)
                {
                    PanicNull(headphone_audio);
                    usbAudioUpdateHeadphoneVolume(headphone_audio);
                }
#endif
            }
        }
        else if (headset_audio->headset->source_connected)
        {
            /* headset Mic & Speaker interfaces are not active - disconnect if connected */
            UsbAudio_DisconnectVoiceMsg(headset_audio);
        }
    }

    if (headphone_audio)
    {
        if (headphone_audio->headphone->spkr_active &&
                !headphone_audio->headphone->source_connected)
        {
            UsbAudio_ConnectAudioMsg(headphone_audio);
            usbAudioUpdateHeadphoneVolume(headphone_audio);
        }
        else if (!headphone_audio->headphone->spkr_active &&
                 headphone_audio->headphone->source_connected)
        {
            UsbAudio_DisconnectAudioMsg(headphone_audio);
        }
    }
}

/****************************************************************************
    Update status for MIC/Speaker for Headset and Headphone.
*/
static void usbAudioUpdateDeviceStatus(usb_audio_info_t *usb_audio, uint8 interface_index)
{
    const usb_audio_interface_config_t *intf_config = &(usb_audio->config->intf_list->intf[interface_index]);
    usb_audio_streaming_info_t  *streaming_info = &(usb_audio->streaming_info[interface_index]);

    bool interface_active = (streaming_info->alt_setting != USB_ALT_SETTING_ZERO) &&
                            (streaming_info->alt_setting != USB_ALT_SETTING_NONE);

    bool headphone_streaming_config_changed = FALSE, headset_streaming_config_changed = FALSE;

    switch(intf_config->type)
    {
        case USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER:
            DEBUG_LOG_INFO("USB Voice: Spkr %x, %x", streaming_info->interface, interface_active);
            PanicNull(usb_audio->headset);
            usb_audio->headset->spkr_active = interface_active;
            if(usb_audio->headset->spkr_streaming_config_index != streaming_info->streaming_desc_index)
            {
                headset_streaming_config_changed = TRUE;
            }
            break;
        case USB_AUDIO_DEVICE_TYPE_VOICE_MIC:
            DEBUG_LOG_INFO("USB Voice: Mic %x, %x",  streaming_info->interface, interface_active);
            PanicNull(usb_audio->headset);
            usb_audio->headset->mic_active = interface_active;
            if(usb_audio->headset->mic_streaming_config_index != streaming_info->streaming_desc_index)
            {
                headset_streaming_config_changed = TRUE;
            }
            break;
        case USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER:
            DEBUG_LOG_INFO("USB Audio: Spkr %x, %x", streaming_info->interface, interface_active);
            PanicNull(usb_audio->headphone);
            usb_audio->headphone->spkr_active = interface_active;
            if(usb_audio->headphone->spkr_streaming_config_index != streaming_info->streaming_desc_index)
            {
                headphone_streaming_config_changed = TRUE;
#ifdef USB_SUPPORT_HEADPHONE_SPKR_IN_VOICE_CHAIN
            /* If alt_setting is changed between two non-zero value,
             * audio chain need to be recreated with new parameters */
            if(interface_active)
            {
                usb_audio_info_t *headset_audio = UsbAudioFd_GetHeadsetInfo(voice_source_usb);
                if(headset_audio && headset_audio->headset->alt_spkr_connected)
                {
                    UsbAudio_HandleVoiceConfigChanged(headset_audio, USB_AUDIO_STREAMING_CONFIG_CHANGED_VOICE_IND, FALSE);
                    /* If disconnected, connection message will send from usbAudioUpdateConnections() */
                }
            }
#endif
            }
            break;
        case USB_AUDIO_DEVICE_TYPE_AUDIO_MIC:
            DEBUG_LOG_INFO("USB Audio: Mic %x, %x",  streaming_info->interface, interface_active);
            PanicNull(usb_audio->headphone);
            usb_audio->headphone->mic_active = interface_active;
            break;
        default:
            DEBUG_LOG_ERROR("usbAudio: Unexpected Device Type %x", intf_config->type);
            Panic();
    }

    /* If alt_setting is changed between two non-zero value,
     * audio chain need to be recreated with new parameters */
    if(interface_active)
    {
        if(headset_streaming_config_changed)
        {
            UsbAudio_HandleVoiceConfigChanged(usb_audio, USB_AUDIO_STREAMING_CONFIG_CHANGED_VOICE_IND, FALSE);
            /* If disconnected, connection message will send from usbAudioUpdateConnections() */
        }

        if(headphone_streaming_config_changed)
        {
            UsbAudio_HandleAudioConfigChanged(usb_audio, USB_AUDIO_STREAMING_CONFIG_CHANGED_AUDIO_IND, FALSE);
            /* If disconnected, connection message will send from usbAudioUpdateConnections() */
        }

    }
    usbAudioUpdateConnections();

    if (USB_IS_TO_HOST_EP(streaming_info->ep_address))
    {
        UsbAudio_SilenceSetVoiceState(streaming_info, interface_active);
    }
    else
    {
        UsbAudio_SilenceSetStreamState(streaming_info, interface_active);
    }
}

/****************************************************************************
    Converts volume DB in steps.
*/
static uint8 usbAudio_VolumeToSteps(const usb_audio_volume_config_t *volume_config,
                                    int8 volume_in_db, uint8 mute_status)
{
    int min_db = volume_config->min_db;
    int max_db = volume_config->max_db;

    if (volume_in_db <= min_db || mute_status)
    {
        return USB_AUDIO_VOLUME_MIN_STEPS;
    }
    if (volume_in_db >= max_db)
    {
        return USB_AUDIO_VOLUME_MAX_STEPS;
    }

    /* scale remaining dB values across remaining steps */
    min_db += 1;
    max_db -= 1;
    int steps = USB_AUDIO_VOLUME_NUM_STEPS - 2;
    int range = max_db - min_db + 1;
    int value = volume_in_db - min_db;

    return (uint8)(USB_AUDIO_VOLUME_MIN_STEPS + 1 + steps * value / range);
}

/****************************************************************************
    Update USB audio volume for active Voice chain
*/
static void usbAudioUpdateHeadsetVolume(usb_audio_info_t *usb_audio)
{
    if(usb_audio->headset != NULL && (usb_audio->headset->spkr_active || usb_audio->headset->mic_active))
    {
        uint8 volume_steps = 0;
        int8  out_vol_db = 0;
        int8  in_vol_db = 0;
        uint8 out_mute = 0;
        uint8 in_mute = 0;

        const usb_audio_volume_config_t *volume_config = &usb_audio->config->volume_config;
        const usb_audio_interface_config_list_t *intf_list = usb_audio->config->intf_list;
        usb_audio_streaming_info_t  *streaming_info = usb_audio->streaming_info;

        for (uint8 i=0; i < usb_audio->num_interfaces; i++)
        {
            if (intf_list->intf[i].type == USB_AUDIO_DEVICE_TYPE_VOICE_MIC)
            {
                in_vol_db =  streaming_info[i].volume_status.volume_db;
                in_mute = streaming_info[i].volume_status.mute_status;
            }
            else if (intf_list->intf[i].type == USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER)
            {
                out_vol_db = streaming_info[i].volume_status.volume_db;
                out_mute =  streaming_info[i].volume_status.mute_status;
            }
        }

        DEBUG_LOG_DEBUG("USB Audio headset: Scaled Gain %ddB Mute %X",
                  out_vol_db, out_mute);

        UNUSED(in_vol_db);

        if(usb_audio->headset->spkr_active)
        {
            volume_steps = usbAudio_VolumeToSteps(volume_config,
                                                  out_vol_db, out_mute);

            if(volume_steps != usb_audio->headset->spkr_volume_steps)
            {
                usb_audio->headset->spkr_volume_steps = volume_steps;

                DEBUG_LOG_DEBUG("USB Audio headset: volume steps = %d",
                        usb_audio->headset->spkr_volume_steps);

                /* Update volume structure */
                Volume_SendVoiceSourceVolumeUpdateRequest(
                                      usb_audio->headset->audio_source,
                                      event_origin_external,
                                      usb_audio->headset->spkr_volume_steps);
            }
        }

        /* Re-configure audio chain */
        appKymeraUsbVoiceMicMute(in_mute);

        /* notify clients of mute status */
        UsbAudio_NotifyClient(USB_AUDIO_REGISTERED_CLIENT_STATUS,
                              (in_mute)?
                                  USB_AUDIO_HEADSET_MIC_MUTE_ACTIVE :
                                  USB_AUDIO_HEADSET_MIC_MUTE_INACTIVE,
                              (Message)(&usb_audio->headset->audio_source));
    }
}

/****************************************************************************
    Update USB audio volume for active Audio chain
*/
static void usbAudioUpdateHeadphoneVolume(usb_audio_info_t *usb_audio)
{
    if(usb_audio->headphone != NULL)
    {
        uint8 volume_steps = 0;
        int8  out_vol_db = 0;
        int8  in_vol_db = 0;
        uint8 out_mute = 0;
        uint8 in_mute = 0;

        const usb_audio_volume_config_t *volume_config = &usb_audio->config->volume_config;
        const usb_audio_interface_config_list_t *intf_list = usb_audio->config->intf_list;
        usb_audio_streaming_info_t  *streaming_info = usb_audio->streaming_info;;

        for (uint8 i=0; i < usb_audio->num_interfaces; i++)
        {
            if (intf_list->intf[i].type == USB_AUDIO_DEVICE_TYPE_AUDIO_MIC)
            {
                in_vol_db =  streaming_info[i].volume_status.volume_db;
                in_mute = streaming_info[i].volume_status.mute_status;
            }
            else if (intf_list->intf[i].type == USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER)
            {
                out_vol_db = streaming_info[i].volume_status.volume_db;
                out_mute =  streaming_info[i].volume_status.mute_status;
            }
        }

        DEBUG_LOG_DEBUG("USB Audio headphone: Scaled Gain %ddB Mute %X",
                  out_vol_db, out_mute);

        UNUSED(in_vol_db);
        UNUSED(in_mute);

        volume_steps = usbAudio_VolumeToSteps(volume_config,
                                              out_vol_db, out_mute);

        if(volume_steps != usb_audio->headphone->spkr_volume_steps)
        {
            usb_audio->headphone->spkr_volume_steps = volume_steps;

            DEBUG_LOG_DEBUG("USB Audio headphone: volume steps = %d",
                      usb_audio->headphone->spkr_volume_steps);

            /* Update volume structure */
            Volume_SendAudioSourceVolumeUpdateRequest(
                        usb_audio->headphone->audio_source,
                        event_origin_external,
                        usb_audio->headphone->spkr_volume_steps);

#ifdef USB_SUPPORT_HEADPHONE_SPKR_IN_VOICE_CHAIN
            usb_audio_info_t *headset_audio = UsbAudioFd_GetHeadsetInfo(voice_source_usb);
            if(headset_audio && headset_audio->headset->alt_spkr_connected)
            {
                /* Update volume structure */
                Volume_SendVoiceSourceVolumeUpdateRequest(
                            voice_source_usb,
                            event_origin_external,
                            usb_audio->headphone->spkr_volume_steps);
            }
#endif
        }

        /* Re-configure audio chain */
        appKymeraUsbAudioMute(out_mute);

        /* notify clients of mute status */
        UsbAudio_NotifyClient(USB_AUDIO_REGISTERED_CLIENT_STATUS,
                             (in_mute) ?
                                 USB_AUDIO_HEADPHONE_MUTE_ACTIVE :
                                 USB_AUDIO_HEADPHONE_MUTE_INACTIVE,
                              (Message)(&usb_audio->headphone->audio_source));
    }
}

#ifndef MESSAGE_USB_EXT_ALT_INTERFACE

/** Update silence streaming parameters
 *
 * In the absence of the audio data, for example while the audio graph
 * is being brought up, device must automatically stream silence to host.
 * On platforms that don't have MESSAGE_USB_EXT_ALT_INTERFACE, this is done
 * in platform firmware and it needs to be aware of the Sampling Rate changes.
 *
 * \param sink USB endpoint sink for the to-host isochronous endpoint
 * \param streaming_info streaming structure associated with the endpoint or
 * "NULL" to disable streaming silence */
static void usbAudioConfigSilenceStreaming(Sink sink, const usb_audio_streaming_info_t *streaming_info)
{
    /* default value "0" means disable streaming silence */
    uint32 streaming_parameters = 0;

    if (streaming_info)
    {
        streaming_parameters = (streaming_info->current_sampling_rate & 0xffffff) |
                             (((streaming_info->channels - 1) & 0xf) << 24) |
                             (((streaming_info->frame_size - 1) & 0xf) << 28);
    }

    bool result = SinkConfigure(sink, VM_SINK_USB_ISOC_STREAM_CONFIG, streaming_parameters);

    DEBUG_LOG_INFO("USB Audio: SinkConfigure(Sink 0x%x, VM_SINK_USB_ISOC_STREAM_CONFIG, 0x%x) result %d",
            sink, streaming_parameters, result);
}

#else

#define usbAudioConfigSilenceStreaming(sink, streaming_info)

#endif /* MESSAGE_USB_EXT_ALT_INTERFACE */

/****************************************************************************
    Update sample rate for audio devices
*/
static void usbAudioSetDeviceSamplingRate(usb_audio_info_t *usb_audio, uint8 interface_index)
{
    const usb_audio_interface_config_t *intf_config = &usb_audio->config->intf_list->intf[interface_index];
    usb_audio_streaming_info_t *streaming_info = &usb_audio->streaming_info[interface_index];
    bool is_headset_rate_modified = FALSE;
    bool is_headphone_rate_modified = FALSE;

    switch(intf_config->type)
    {
        case USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER:
        {
            PanicNull(usb_audio->headset);
            if(usb_audio->headset->spkr_active && usb_audio->headset->spkr_sample_rate != streaming_info->current_sampling_rate)
            {
                DEBUG_LOG_INFO("USB Audio: Headset Spkr sample rate %d -> %d",
                               usb_audio->headset->spkr_sample_rate,  streaming_info->current_sampling_rate);
                is_headset_rate_modified = TRUE;
            }
            break;
        }
        case USB_AUDIO_DEVICE_TYPE_VOICE_MIC:
        {
            PanicNull(usb_audio->headset);

            usbAudioConfigSilenceStreaming(usb_audio->headset->mic_sink, streaming_info);

            if(usb_audio->headset->mic_active && usb_audio->headset->mic_sample_rate != streaming_info->current_sampling_rate)
            {
                DEBUG_LOG_INFO("USB Audio: Headset Mic sample rate %d -> %d",
                               usb_audio->headset->mic_sample_rate,  streaming_info->current_sampling_rate);
                is_headset_rate_modified = TRUE;
            }
            break;
        }
        case USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER:
        {
            PanicNull(usb_audio->headphone);
            if(usb_audio->headphone->spkr_active && usb_audio->headphone->spkr_sample_rate != streaming_info->current_sampling_rate)
            {
                DEBUG_LOG_INFO("USB Audio: Headphone Spkr sample rate %d -> %d",
                               usb_audio->headphone->spkr_sample_rate,  streaming_info->current_sampling_rate);
                is_headphone_rate_modified = TRUE;
            }
            break;
        }
        default:
            DEBUG_LOG_ERROR("usbAudio: Set Sampling Rate Unexpected Device Type %x", intf_config->type);
            Panic();
    }

    /*If sample rate is read by audio source then first disconnect existing chain
     *  and connect with new sample rate.*/
    if(is_headset_rate_modified)
    {
        UsbAudio_HandleVoiceConfigChanged(usb_audio, USB_AUDIO_SAMPLE_RATE_VOICE_IND, TRUE);
    }

    if(is_headphone_rate_modified)
    {

        UsbAudio_HandleAudioConfigChanged(usb_audio, USB_AUDIO_SAMPLE_RATE_AUDIO_IND, TRUE);

#ifdef USB_SUPPORT_HEADPHONE_SPKR_IN_VOICE_CHAIN
        usb_audio_info_t *headset_audio = UsbAudioFd_GetHeadsetInfo(voice_source_usb);
        if(headset_audio && headset_audio->headset->alt_spkr_connected)
        {
            UsbAudio_HandleVoiceConfigChanged(headset_audio, USB_AUDIO_SAMPLE_RATE_VOICE_IND, TRUE);
        }
#endif
    }
}

usb_audio_streaming_info_t *UsbAudio_GetStreamingInfo(usb_audio_info_t *usb_audio,
                                                      usb_audio_device_type_t type)
{
     for (uint8 i=0; i < usb_audio->num_interfaces; i++)
     {
         if (usb_audio->config->intf_list->intf[i].type == type)
         {
             return &(usb_audio->streaming_info[i]);
         }
     }

     return NULL;
}


usb_audio_info_t *UsbAudioFd_GetHeadphoneInfo(audio_source_t source)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();
    while (usb_audio)
    {
        if(usb_audio->headphone != NULL &&
            usb_audio->headphone->audio_source == source)
        {
            break;
        }
        usb_audio = usb_audio->next;
    }

    return usb_audio;
}

usb_audio_info_t *UsbAudio_FindInfoBySource(Source source)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();
    while (usb_audio)
    {
        if(usb_audio->headphone != NULL &&
            usb_audio->headphone->spkr_src == source)
        {
            break;
        }
        if(usb_audio->headset != NULL &&
            usb_audio->headset->spkr_src == source)
        {
            break;
        }
        usb_audio = usb_audio->next;
    }

    return usb_audio;
}

usb_audio_info_t *UsbAudioFd_GetHeadsetInfo(voice_source_t source)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();
    while (usb_audio)
    {
        if(usb_audio->headset != NULL &&
            usb_audio->headset->audio_source == source)
        {
            break;
        }
        usb_audio = usb_audio->next;
    }

    return usb_audio;
}

device_t UsbAudio_GetDevice(void)
{
    deviceType type = DEVICE_TYPE_USB_HOST;

    return DeviceList_GetFirstDeviceWithPropertyValue(device_property_type, &type, sizeof(type));
}

/* To create USB device and add to device list, if not already created */
static device_t usbAudio_CreateDevice(void)
{
    device_t device = UsbAudio_GetDevice();

    if (device)
    {
        DEBUG_LOG_INFO("usbAudio_CreateDevice: Device already created");
    }
    else
    {
        deviceType type = DEVICE_TYPE_USB_HOST;

        DEBUG_LOG_INFO("usbAudio_CreateDevice: Creating Device and adding to DeviceList");
        device = Device_Create();
        if (device)
        {
            Device_SetProperty(device, device_property_type, &type, sizeof(deviceType));

            if (!DeviceList_AddDevice(device))
            {
                Device_Destroy(&device);
                /* As can't add the device to the device list so no point going forward */
                DEBUG_LOG_ERROR("usbAudio_CreateDevice: can't add device to the device list");
                Panic();
            }
        }
        else
        {
            DEBUG_LOG_ERROR("usbAudio_CreateDevice: can't create device");
            Panic();
        }
    }

    return device;
}

/* Delete USB device from device list and cache. */
static void usbAudio_DestroyDevice(void)
{
    device_t device = UsbAudio_GetDevice();

    if (device)
    {
        DEBUG_LOG_INFO("UsbAudio_DestroyDevice");
        DeviceList_RemoveDevice(device);
        Device_Destroy(&device);
    }
    else
    {
        DEBUG_LOG_INFO("UsbAudio_DestroyDevice: Device not available");
    }
}

/****************************************************************************
    Handle USB device and audio class messages.
*/
static void UsbAudio_HandleMessage(usb_device_index_t device_index,
                                     MessageId id, Message message)
{
    UNUSED(message);

    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();

    DEBUG_LOG_WARN("USB Audio device %d event MESSAGE:0x%x", device_index, id);

    while (usb_audio)
    {
        if(device_index == usb_audio->device_index)
        {
            switch(id)
            {
                case MESSAGE_USB_DETACHED:
                case MESSAGE_USB_DECONFIGURED:
                    {
                        device_t device = UsbAudio_GetDevice();

                        if(device)
                        {
                            usbAudio_DestroyDevice();
                        }

                        usb_audio->usb_fn_uac->Reset(usb_audio->class_ctx);
                        if(usb_audio->headphone != NULL)
                        {
                            usb_audio->headphone->spkr_active = FALSE;
                            usb_audio->headphone->mic_active = FALSE;

                            if (usb_audio->headphone->source_connected)
                            {
                                UsbAudio_DisconnectAudioMsg(usb_audio);
                            }
                            if (UsbAudio_isRxGraphActive())
                            {
                                usb_audio_streaming_info_t *streaming_info =
                                        UsbAudio_GetStreamingInfo(usb_audio,
                                                                  USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER);

                                UsbAudio_SilenceSetStreamState(streaming_info, FALSE);
                            }
                            /* with respect to usb audio, this means the host as disconnected audio */
                            UsbSource_SetAudioContext(context_audio_disconnected);
                        }
                        if(usb_audio->headset != NULL)
                        {
                            usb_audio->headset->spkr_active = FALSE;
                            usb_audio->headset->mic_active = FALSE;

                            if (usb_audio->headset->source_connected)
                            {
                                UsbAudio_DisconnectVoiceMsg(usb_audio);
                            }

                            if (UsbAudio_isTxGraphActive())
                            {
                                usb_audio_streaming_info_t *streaming_info =
                                        UsbAudio_GetStreamingInfo(usb_audio, USB_AUDIO_DEVICE_TYPE_VOICE_MIC);

                                UsbAudio_SilenceSetVoiceState(streaming_info, FALSE);
                            }
                            /* with respect to usb audio, this means the host as disconnected audio */
                            UsbSource_SetVoiceState(USB_SOURCE_VOICE_DISCONNECTED);
                        }
                    }
                    break;

                case MESSAGE_USB_ENUMERATED:
                    {
                        /* Creating USB device for supporting routing of MRU device */
                        usbAudio_CreateDevice();
                        /* Updating MRU index of usb_device */
                        DeviceList_DeviceWasUsed(UsbAudio_GetDevice());
                        if(usb_audio->headphone != NULL)
                        {
                            /* with respect to usb audio, this means that host has just connected and not streaming */
                            UsbSource_SetAudioContext(context_audio_connected);
                        }

                        if(usb_audio->headset != NULL)
                        {
                            /* with respect to usb voice, this means that host has just connected and not streaming */
                            UsbSource_SetVoiceState(USB_SOURCE_VOICE_CONNECTED);
                        }
                    }
                    break;
                default:
                    DEBUG_LOG_VERBOSE("Unhandled USB message MESSAGE:0x%x", id);
                    break;
            }
        }
        usb_audio = usb_audio->next;
    }
}

/* Handle event from USB audio class driver */
static void UsbAudio_ClassEvent(uac_ctx_t class_ctx,
                                uint8 interface_index,
                                uac_message_t uac_message)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();

    DEBUG_LOG_INFO("UsbAudio_ClassEvent intf_index %d  enum:message:%d", interface_index, uac_message);

    while (usb_audio)
    {
        if(usb_audio->class_ctx == class_ctx)
        {
            switch(uac_message)
            {
                case USB_AUDIO_CLASS_MSG_LEVELS:
                    if (usb_audio->headset)
                    {
                        usbAudioUpdateHeadsetVolume(usb_audio);
                    }
                    if (usb_audio->headphone)
                    {
                        usbAudioUpdateHeadphoneVolume(usb_audio);
                    }
                    break;

                case USB_AUDIO_CLASS_MSG_SAMPLE_RATE:
                    usbAudioSetDeviceSamplingRate(usb_audio, interface_index);
                    break;

                case USB_AUDIO_CLASS_MSG_ALT_INTF:
                    usbAudioUpdateDeviceStatus(usb_audio, interface_index);
                    break;

                default:
                    DEBUG_LOG_VERBOSE("Unhandled USB message 0x%x", uac_message);
                    break;
            }

            break;
        }
        usb_audio = usb_audio->next;
    }
}


/* Register for USB device events */
static void usbAudio_RegisterForUsbDeviceEvents(usb_device_index_t device_index)
{
    usb_audio_info_t *usb_audio_data = USB_AUDIO_GET_DATA();
    while (usb_audio_data)
    {
        if(usb_audio_data->device_index == device_index)
        {
            break;
        }
        usb_audio_data = usb_audio_data->next;
    }

    if(usb_audio_data == NULL)
    {
        UsbDevice_RegisterEventHandler(device_index, UsbAudio_HandleMessage);
    }
}

static usb_audio_device_type_t usbAudio_GetDeviceTypesCreated(void)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();
    usb_audio_device_type_t audio_device_types = 0;

    while (usb_audio)
    {
        const usb_audio_interface_config_list_t *intf_list = usb_audio->config->intf_list;

        for(uint8 index = 0; index<intf_list->num_interfaces;index++)
        {
            audio_device_types |= intf_list->intf[index].type;
        }

        usb_audio = usb_audio->next;
    }

    return audio_device_types;
}

/****************************************************************************
    Create context for USB Audio function driver.
*/
static usb_class_context_t usbAudio_Create(usb_device_index_t device_index,
                                usb_class_interface_config_data_t config_data)
{
    const usb_audio_config_params_t *config = (const usb_audio_config_params_t *)config_data;
    usb_audio_info_t *usb_audio;
    usb_audio_device_type_t audio_device_types, audio_device_type;
    usb_audio_device_type_t device_types_created = usbAudio_GetDeviceTypesCreated();

    /* Configuration data is required */
    PanicZero(config);
    PanicZero(config->intf_list);
    PanicZero(config->intf_list->num_interfaces);

    usb_audio = (usb_audio_info_t *)PanicUnlessMalloc(sizeof(usb_audio_info_t));
    memset(usb_audio, 0, sizeof(usb_audio_info_t));

    usb_audio->usb_fn_uac = usbAudioGetFnTbl(config->rev);
    /* Requested device class revision must be supported */
    PanicZero(usb_audio->usb_fn_uac);

    usb_audio->num_interfaces = config->intf_list->num_interfaces;

    /* Check only supported types requested */
    audio_device_types = 0;
    for (uint8 i=0; i < usb_audio->num_interfaces; i++)
    {
        audio_device_type = config->intf_list->intf[i].type;
        /* Same type of device can only be created once*/
        PanicNotZero(device_types_created & audio_device_type);
        PanicNotZero(audio_device_types & audio_device_type);
        audio_device_types |= audio_device_type;
    }

    DEBUG_LOG_ALWAYS("UsbAudio_Create audio_device_types %X  num_interfaces:%x",
            audio_device_types, usb_audio->num_interfaces);

    /* At least one supported device typs must be requested */
    PanicZero(audio_device_types & USB_AUDIO_SUPPORTED_DEVICE_TYPES);
    /* Requested device types must be supported*/
    PanicNotZero(audio_device_types & ~USB_AUDIO_SUPPORTED_DEVICE_TYPES);

    usb_audio->device_index = device_index;
    usb_audio->config = config;
    /* create USB audio class instance */
    usb_audio->class_ctx = usb_audio->usb_fn_uac->Create(usb_audio->device_index,
                                              config,
                                              &(usb_audio->streaming_info),
                                              UsbAudio_ClassEvent);

    if (audio_device_types & (USB_AUDIO_DEVICE_TYPE_VOICE_SPEAKER |
                                  USB_AUDIO_DEVICE_TYPE_VOICE_MIC))
    {
        usbAudioAddHeadset(usb_audio);
    }

    if (audio_device_types & (USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER |
                              USB_AUDIO_DEVICE_TYPE_AUDIO_MIC))
    {
        usbAudioAddHeadphone(usb_audio);
    }

    usbAudio_RegisterForUsbDeviceEvents(device_index);

    usb_audio->next = usbaudio_globaldata;
    usbaudio_globaldata = usb_audio;

    return usb_audio;
}

static bool usbAudio_FreeData(usb_audio_info_t *usb_audio)
{
    bool postpone_delete = FALSE;

    if (usb_audio->headset)
    {
        DEBUG_LOG_INFO("UAC_FD: FreeData headset chain %d silence %d",
                usb_audio->headset->chain_active,
                UsbAudio_isTxGraphActive());

        if (usb_audio->headset->chain_active ||
                UsbAudio_isTxGraphActive())
        {
            postpone_delete = TRUE;
        }

        if (usb_audio->headset->source_connected)
        {
            UsbAudio_DisconnectVoiceMsg(usb_audio);
        }

        if (UsbAudio_isTxGraphActive())
        {
            usb_audio_streaming_info_t *streaming_info =
                    UsbAudio_GetStreamingInfo(usb_audio, USB_AUDIO_DEVICE_TYPE_VOICE_MIC);

            UsbAudio_SilenceSetVoiceState(streaming_info, FALSE);
        }
    }

    if (usb_audio->headphone)
    {
        DEBUG_LOG_INFO("UAC_FD: FreeData headphone chain %d silence %d",
                usb_audio->headphone->chain_active,
                UsbAudio_isRxGraphActive());

        if (usb_audio->headphone->chain_active ||
                UsbAudio_isRxGraphActive())
        {
            postpone_delete = TRUE;
        }

        if (usb_audio->headphone->source_connected)
        {
            UsbAudio_DisconnectAudioMsg(usb_audio);
        }

        if (UsbAudio_isRxGraphActive())
        {
            usb_audio_streaming_info_t *streaming_info =
                    UsbAudio_GetStreamingInfo(usb_audio,
                                              USB_AUDIO_DEVICE_TYPE_AUDIO_SPEAKER);
            UsbAudio_SilenceSetStreamState(streaming_info, FALSE);
        }
    }

    if (postpone_delete)
    {
        usb_audio->is_pending_delete = 1;
        return FALSE;
    }

    if(usb_audio->headset != NULL)
    {
        UsbSource_SetVoiceState(USB_SOURCE_VOICE_DISCONNECTED);
        UsbSource_DeregisterVoiceControl();

        free(usb_audio->headset);
        usb_audio->headset = NULL;
    }

    if(usb_audio->headphone != NULL)
    {
        UsbSource_SetAudioContext(context_audio_disconnected);
        UsbSource_DeregisterAudioControl();

        free(usb_audio->headphone);
        usb_audio->headphone = NULL;
    }

    usb_audio->usb_fn_uac->Delete(usb_audio->class_ctx);

    return TRUE;
}

usb_result_t UsbAudio_TryFreeData(usb_audio_info_t *usb_audio)
{
    usb_audio_info_t **dp = &usbaudio_globaldata;

    while (*dp)
    {
        if (*dp == usb_audio)
        {
            if (!usbAudio_FreeData(usb_audio))
            {
                DEBUG_LOG_DEBUG("usbAudio_FreeData: BUSY");
                return USB_RESULT_BUSY;
            }
            else
            {
                DEBUG_LOG_DEBUG("usbAudio_FreeData: OK");
                /* Before calling UsbDevice_ReleaseClass, audio device instance
                 * should be removed from the linked list. */
                *dp = usb_audio->next;
                if (usb_audio->is_pending_delete)
                {
                    UsbDevice_ReleaseClass(usb_audio->device_index, usb_audio);
                }

                free(usb_audio);
                return USB_RESULT_OK;
            }
        }
        dp = &((*dp)->next);
    }
    return USB_RESULT_NOT_FOUND;
}

static usb_result_t usbAudio_Destroy(usb_class_context_t context)
{
    DEBUG_LOG_WARN("UsbAudio: closed");
    return UsbAudio_TryFreeData((usb_audio_info_t *)context);
}

static usb_interface_result_t usbAudio_SetInterface(usb_class_context_t context,
                                  uint16 interface, uint16 altsetting)
{
    usb_audio_info_t *usb_audio = USB_AUDIO_GET_DATA();

    while (usb_audio)
    {
        if(context == usb_audio)
        {
            if (usb_audio->usb_fn_uac->SetAudioInterface)
            {
                return usb_audio->usb_fn_uac->SetAudioInterface(usb_audio->class_ctx,
                    interface, altsetting);
            }
            break;
        }
        usb_audio = usb_audio->next;
    }

    return USB_INTERFACE_NOT_HANDLED;
}

const usb_class_interface_cb_t UsbAudio_Callbacks =
{
    .Create = usbAudio_Create,
    .Destroy = usbAudio_Destroy,
    .SetInterface = usbAudio_SetInterface,
};

